/* Orbas:
 *     A open source CORBA Specification implementation from Huihoo.
 *
 * Copyright 2002-2003 Huihoo.org, Inc. All Right Reserved.
 *
 * This software is licensed under LGPL license.
 * See terms of license at gnu.org.
 *
 * For more information, visit:
 *
 * http://www.huihoo.org/orbas
 */



package org.huihoo.orbas.orb.cdr;

/**
 * <p>Description: </p>
 * CDR encode stream.
 * <p>Copyright (c) 2002,2003</p>
 * <p>Company: <a href="http://www.huihoo.org/">huihoo.org</a></p>
 * @author <a href="http://www.huihoo.org/~mep">mep(mep@huihoo.com)</a>
 * @see <a href="http://www.huihoo.org/orbas">http://www.huihoo.org/orbas</a>
 * @version 1.0
 */


import com.cognition.util.j2me.DataTypeUtil;
import org.omg.CORBA.ORB;
import org.omg.CORBA.TCKind;

public class CDROutputStream extends org.omg.CORBA_2_3.portable.OutputStream {
    private ORB orb;
    private byte[] buffer;
    private int nextByteToWrite;
    
    private java.util.Hashtable valueList;
    private int urlIndex;
    private int level = 0;

  	protected final static int VALUE_NO_CODEBASE = 0;
  	
  	protected final static int VALUE_CODEBASE = 1;
  	
  	protected final static int VALUE_NO_TYPE_INFORMATION = 0;
  	
  	protected final static int SINGLE_TYPE_INFORMATION = 2;
  	
  	protected final static int MULTIPLE_TYPE_INFORMATION = 6;
  	
  	protected final static int CHUNK = 8;
  	
  	protected final static int NO_CHUNK = 0;
  
  	protected final static int OPEN_ENCAPS = 0xFFFFFFFF;
	
    //big endian by default
    public CDROutputStream(ORB orb) {
        this.orb = orb;
        
        buffer = new byte[512];
        nextByteToWrite = 0;
        
        valueList = new java.util.Hashtable(); 
        urlIndex = -1;
    }

    public CDROutputStream(byte[] buf, int len) {
        buffer = buf;
        nextByteToWrite = 0;
        
        valueList = new java.util.Hashtable();
    }

    public org.omg.CORBA.portable.InputStream create_input_stream() {
    	
    	//what does this operation means?
        int length = nextByteToWrite + 1;
        byte[] buf = new byte[length];

        System.arraycopy(buffer, 0, buf,0, length);
            
        //default big endian assumed
        return new CDRInputStream(orb, buf);
    }

    public void write_encapsulation(CDROutputStream out) {
    	int length = out.getLength();

        write_long(length);
        write_octet_array(out.getBuffer(), 0, length);
    }

    public void write_boolean(boolean value) {
    	makeRoom(1);
    	buffer[nextByteToWrite++] = value ? (byte) 1 : (byte) 0;
    }

    public void write_boolean_array(boolean[] value, int offset, int length) {
    	makeRoom(length);
    	
    	for(int i = 0;i < length;i++){
    		write_boolean(value[i + offset]);
    	}
    }

    public void write_octet(byte value) {
    	makeRoom(1);
    	buffer[nextByteToWrite++] = value;
    }

    public void write_octet_array(byte[] value, int offset, int length) {
    	makeRoom(length);
    	
    	for(int i = 0;i < length;i++){
    		write_octet(value[i + offset]);
    	}
    }

    public void write_char(char c) {
    	makeRoom(1);
    	
    	write_octet((byte) (c & 0xFF));
    }

    public void write_char_array(char[] value, int offset, int length) {
    	makeRoom(length);
    	
    	for(int i = 0;i < length;i++){
    		write_char(value[i + offset]);
    	}
    }

    public void write_string(String s) {
    	
    	int length = s.length();
    	
    	write_long(length + 1);
    	
    	makeRoom(length + 1);
    	
    	for(int i = 0; i < length; i++){
    		write_char(s.charAt(i));
    	}
    	
    	write_char((char) 0);
    }

    public void write_short(short value) {
    	padding(2);
    	
        makeRoom(2);
    	
    	buffer[nextByteToWrite++] = (byte) ((value >>> 8) & 0xFF);
        buffer[nextByteToWrite++] = (byte) (value & 0xFF);
    }

    public void write_short_array(short[] value, int offset, int length) {
    	makeRoom(length * 2);
    	
    	for(int i = 0;i < length;i++){
    		write_short(value[i + offset]);
    	}
    }

    public void write_long(int value) {
    	padding(4);
    	
        makeRoom(4);
        
   	    buffer[nextByteToWrite++] = (byte) ((value >>> 24) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 16) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 8) & 0xFF);
        buffer[nextByteToWrite++] = (byte) (value & 0xFF);
    }

    public void write_long_array(int[] value, int offset, int length) {
    	makeRoom(length * 4);
    	
    	for(int i = 0;i < length;i++){
    		write_long(value[i + offset]);
    	}
    }

    public void write_wchar(char c) {
    	write_short((short) c);
    }

    public void write_wchar_array(char[] value, int offset, int length) {
        
    	makeRoom(length * 2);
    	
    	for(int i = 0;i < length;i++){
    		write_wchar(value[i + offset]);
    	}
    }

    public void write_wstring(String s) {
        
    	int length = s.length();
    	
    	makeRoom((length + 1) * 2);
    	
    	write_long(length + 1);
    	
    	for(int i = 0;i < length;i++){
    		write_wchar(s.charAt(i));
    	}
    	
    	write_wchar((char) 0);
    }

    public void write_float(float value) {
        
    	write_long(Float.floatToIntBits(value));
    }

    public void write_float_array(float[] value, int offset, int length) {
        
    	makeRoom(length * 4);
    	
    	for(int i = 0;i < length;i++){
    		write_float(value[i + offset]);
    	}
    }

    public void write_double(double value) {
        
    	write_longlong(Double.doubleToLongBits(value));
    }

    public void write_double_array(double[] value, int offset, int length) {
    	makeRoom(length * 8);
    	
    	for(int i = 0;i < length;i++){
    		write_double(value[i + offset]);
    	}
    }

    public void write_longlong(long value) {
    	padding(8);
    	
        makeRoom(8);
        
   	    buffer[nextByteToWrite++] = (byte) ((value >>> 56) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 48) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 40) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 32) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 24) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 16) & 0xFF);
        buffer[nextByteToWrite++] = (byte) ((value >>> 8) & 0xFF);
        buffer[nextByteToWrite++] = (byte) (value & 0xFF);
    }

    public void write_longlong_array(long[] value, int offset, int length) {
        
    	makeRoom(length * 8);
    	
    	for(int i = 0;i < length;i++){
    		write_longlong(value[i + offset]);
    	}
    }

    public void write_ulong(int value) {
        
    	write_long(value);
    }

    public void write_ulong_array(int[] value, int offset, int length) {
        
    	write_long_array(value, offset, length);
    }

    public void write_ulonglong(long value) {

    	write_longlong(value);
    }

    public void write_ulonglong_array(long[] value, int offset, int length) {
        
    	write_longlong_array(value, offset, length);
    }

    public void write_ushort(short value) {
        
    	write_short(value);
    }

    public void write_ushort_array(short[] value, int offset, int length) {
        
    	write_short_array(value, offset, length);
    }

    public void write_any(org.omg.CORBA.Any value) {
        
    	write_TypeCode(value.type());
        value.write_value(this);
    }

    public void write_Object(org.omg.CORBA.Object value) {
        org.omg.CORBA.portable.ObjectImpl objectImpl = (org.omg.CORBA.portable.ObjectImpl)value;
        
        if(objectImpl == null){
            //report an error
            System.out.println("CDROutputStream: Not a CORBA Object.");
            return;
        }
        
        org.huihoo.orbas.orb.types.ObjRefDelegate delegate = (org.huihoo.orbas.orb.types.ObjRefDelegate)(objectImpl._get_delegate());
        
        if(delegate == null){
            //report an error
            System.out.println("CDROutputStream: Not a CORBA Object.Can NOT get delegate");
            return;
        }
        
        org.omg.IOP.IOR ior = delegate.getIor();
        
        write_boolean(false);
        org.omg.IOP.IORHelper.write(this,ior);
    }

    public void write_TypeCode(org.omg.CORBA.TypeCode value) {
        int tcKind = value.kind().value();
        int memCount = 0;
        
        try {
            switch(tcKind){
            
            case TCKind._tk_null:
            case TCKind._tk_void:
            case TCKind._tk_short:
            case TCKind._tk_long:
            case TCKind._tk_ushort:
            case TCKind._tk_ulong:
            case TCKind._tk_float:
            case TCKind._tk_double:
            case TCKind._tk_boolean:
            case TCKind._tk_char:
            case TCKind._tk_octet:
            case TCKind._tk_any:
            case TCKind._tk_TypeCode:
            case TCKind._tk_longlong:
            case TCKind._tk_ulonglong:
            case TCKind._tk_longdouble:
            case TCKind._tk_wchar:
                write_long(tcKind);
                return;
            
            case TCKind._tk_objref:
                write_long(tcKind);
                write_string(value.id());
                write_string(value.name());
                return;
            case TCKind._tk_enum:
                write_long(tcKind);
                memCount = value.member_count();
                write_string(value.id());
                write_string(value.name());
                write_long(memCount);
                
                for (int i = 0; i < memCount; i++) {
                    write_string(value.member_name(i));
                }
                
                return;
    
            case TCKind._tk_struct:
                write_long(tcKind);
                memCount = value.member_count();
                write_string(value.id());
                write_string(value.name());
                write_long(memCount);
                
                for (int i = 0; i < memCount; i++) {
                    write_string(value.member_name(i));
                    write_TypeCode(value.member_type(i));
                }
                
                return;
    
            case TCKind._tk_except:
                write_long(tcKind);
                memCount = value.member_count();
                write_string(value.id());
                write_string(value.name());
                write_long(memCount);
                
                for (int i = 0; i < memCount; i++) {
                    write_string(value.member_name(i));
                    write_TypeCode(value.member_type(i));
                }
                
                return;
    
            case TCKind._tk_union:
                write_long(tcKind);
                write_string(value.id());
                write_string(value.name());
                write_TypeCode(value.discriminator_type());
                write_long(value.default_index());
                memCount = value.member_count();
                write_long(memCount);
                
                for (int i = 0; i < memCount; i++) {
                    //Unknow what to do
//                    throw new org.omg.CORBA.NO_IMPLEMENT();
                    
                    write_any(value.member_label(i));
                    write_string(value.member_name(i));
                    write_TypeCode(value.member_type(i));
                    
                }
                
                return;
    
            case TCKind._tk_alias:
                write_long(tcKind);
                write_string(value.id());
                write_string(value.name());
                write_TypeCode(value.get_compact_typecode());
                return;
                
            case TCKind._tk_abstract_interface:
                write_long(tcKind);
                write_string(value.id());
                write_string(value.name());
                return;
    
            case TCKind._tk_sequence:
                write_long(tcKind);
                write_long(value.length());
                write_TypeCode(value.get_compact_typecode());
                return;
            case TCKind._tk_string:
                
                write_long(tcKind);
                write_long(0);
                return;
            case TCKind._tk_array:
                write_long(tcKind);
                write_TypeCode(value.get_compact_typecode());
                write_long(value.length());
                return;
            case TCKind._tk_wstring:
                write_long(tcKind);
                write_long(0);
                return;
            case TCKind._tk_fixed:
                throw new org.omg.CORBA.NO_IMPLEMENT();
//                return;
            case TCKind._tk_value:
                throw new org.omg.CORBA.NO_IMPLEMENT();
//                return;
            case TCKind._tk_value_box:
                throw new org.omg.CORBA.NO_IMPLEMENT();
//                return;
            case TCKind._tk_native:
                write_long(tcKind);
                write_string(value.id());
                write_string(value.name());
                return;
            case TCKind._tk_local_interface:
                write_long(tcKind);
                write_string(value.id());
                write_string(value.name());
                return;
            default:
                //report an error
                System.out.println("CDROutputStream Invalid TCKind code: " + tcKind);
                return;
                
            }
        }
        catch(org.omg.CORBA.TypeCodePackage.BadKind e1) {
            System.out.println("Bad Kind Exception while writing TypeCode");
            return;
        }
        catch(org.omg.CORBA.TypeCodePackage.Bounds e2) {
            System.out.println("Bad Kind Exception while writing TypeCode");
            return;
        }
        
    }

    public void write_Context(org.omg.CORBA.Context ctx,
                            org.omg.CORBA.ContextList contexts) {
        return;
    }
    

    
    public void write_value(com.cognition.util.j2me.J2MESerializable value) {
      // Check if null value
  		if ( value == null ) {
  			write_long(0);
  			return;		
  		}
  		
			// Check if the value is already marshalled
			java.lang.Integer offset = ( java.lang.Integer ) valueList.get( value ); 
			if ( offset != null ) {
			  //already marshlled, then write offset
				write_long(0xffffffff);
				write_long( offset.intValue() );
				return;
			}
		  
			valueList.put( value, new Integer( nextByteToWrite ) );
			
			String uri = null;
			//custom value
			if ( value instanceof org.omg.CORBA.portable.CustomValue ) {
  		  org.omg.CORBA.portable.CustomValue customValue;
  		  customValue = ( org.omg.CORBA.portable.CustomValue ) value;
  		  String [] id = customValue._truncatable_ids();
  		  
  		  int tag = 0;
  		  
  		  uri = getURLCodeBase();
  		  
  		  if ( id.length > 1 ) {
  				tag = 0x7fffff00 + MULTIPLE_TYPE_INFORMATION + CHUNK ;
  			}
  			else {
  				tag = 0x7fffff00 + SINGLE_TYPE_INFORMATION + CHUNK;
  			}
  			
  			if ( uri == null ) {
  				tag = tag + VALUE_NO_CODEBASE;
  			}
  			else {
  				tag = tag + VALUE_CODEBASE;
  			}
  				
  			write_long(tag);
  			
  			if  ( uri != null ) {
  				if ( urlIndex != -1 ) {
  					write_ulong( 0xffffffff );
  					write_ulong( urlIndex );
  				}
  				else {
  					padding( 4 );
  					urlIndex = nextByteToWrite;
  					write_string( uri );
  				}
  			}
  			
  			level++;
			
  			// Put Repository Id
  			
  			if ( id.length > 1 ) {
  				write_long( id.length );
  			}
  			
  			for ( int i=0; i<id.length; i++ ) {
  				write_string( id[i] );
  			}
  			
  			// Custom marshalling			
  			CDROutputStream output = new CDROutputStream(orb);
  			org.huihoo.orbas.orb.io.OutputStream cdrOut = new org.huihoo.orbas.orb.io.OutputStream(output);
  			customValue.marshal( cdrOut );
  			
  			write_long( output.getLength());
  						
  			byte[] buffer = output.getBuffer();
  			write_octet_array( buffer, 0, output.getLength());
  			
  			write_long(-level);	
  			level--;
  			return;
  		}
  		
  		if ( value instanceof org.omg.CORBA.portable.StreamableValue ) {
  			org.omg.CORBA.portable.StreamableValue streamValue;
  			streamValue = ( org.omg.CORBA.portable.StreamableValue ) value;
  			String [] id = streamValue._truncatable_ids();
  			
  			int tag = 0;
  			
  			uri = getURLCodeBase();
  
  			if ( id.length > 1 ) {
  				tag = 0x7fffff00 + MULTIPLE_TYPE_INFORMATION + CHUNK ;
  			}
  			else {
  				tag = 0x7fffff00 + SINGLE_TYPE_INFORMATION + NO_CHUNK;
  			}
  			
  			if ( uri == null ) {
  				tag = tag + VALUE_NO_CODEBASE;
  			}
  			else {
  				tag = tag + VALUE_CODEBASE;
  			}
  				
  			write_long(tag);
  			
  			if  ( uri != null ) {
  				write_string( uri );
  			}
  		
  			if ( id.length > 1 ) {
  				write_long( id.length );
  			}
  			
  			for ( int i=0; i<id.length; i++ ) {
  				write_string( id[i] );
  			}
  				
  		  if ( id.length > 1 ) {
  					
  				level++;
  				CDROutputStream output = new CDROutputStream(orb);
  				streamValue._write(output);
  				write_long( output.getLength() );
  							
  				byte [] buffer = output.getBuffer();
  				write_octet_array( buffer, 0, output.getLength() );
  			
  				write_long(-level);				
  				level--;
  			}
  			else {
  				streamValue._write(this);
  			}
  		}
  		
  		if (!( value instanceof org.omg.CORBA.portable.StreamableValue ))	{
  			if ( value instanceof org.omg.CORBA.portable.ValueBase ) {
  				String id = ((org.omg.CORBA.portable.ValueBase)value)._truncatable_ids()[0];
  				write_value( value, id );
  				
  				return;
  			}
  			
  			throw new org.omg.CORBA.BAD_PARAM();
  		}
    }

    public void write_value(com.cognition.util.j2me.J2MESerializable value, java.lang.String rep_id) {
      // Check if null value
  		if ( value == null ) {
  			write_long(0);
  			return;		
  		}
  		
			// Check if the value is already marshalled
			java.lang.Integer offset = ( java.lang.Integer ) valueList.get( value ); 
			if ( offset != null ) {
			  //already marshlled, then write offset
				write_long(0xffffffff);
				write_long( offset.intValue() );
				return;
				
			}
		  
			valueList.put( value, new Integer( nextByteToWrite ) );
			
			if (!( value instanceof org.omg.CORBA.portable.StreamableValue )) {
			  //load help class
			  int index = 0;
    		java.lang.Class clzHelper = null;
    		String baseName = rep_id.substring(4, 
                    DataTypeUtil.lastIndexOf(rep_id, ":" ));
    				
    		baseName = baseName.replace('/','.');
    
    		String BoxedValueHelperName = baseName + "Helper";
    		
    		while ( true ) {
    		
    			try {
      			clzHelper = java.lang.Class.forName( BoxedValueHelperName );
      		}
      		catch ( ClassNotFoundException ex ) {
      		  System.out.println("Unable to load help class by id of " + rep_id);
      			throw new org.omg.CORBA.MARSHAL();
      		}
      		
      		if ( clzHelper != null ) {
  				  break;
  			  }
  			
    			index = BoxedValueHelperName.indexOf(".");
    			
    			if ( index == -1 ) {
    				break;
    			}
    			
    			BoxedValueHelperName = BoxedValueHelperName.substring( index+1, BoxedValueHelperName.length() );
    		}
    		
    		if ( clzHelper == null ) {
    			System.out.println("Unable to load help class by id of " + rep_id);
      		throw new org.omg.CORBA.MARSHAL();
    		}
    		
    		org.omg.CORBA.portable.BoxedValueHelper helper = null;
  			try {
  				helper = ( org.omg.CORBA.portable.BoxedValueHelper ) clzHelper.newInstance();
  			}
  			catch ( InstantiationException e ) {
  			  System.out.println("Unable to instantiate value help class");
  				throw new org.omg.CORBA.MARSHAL();
  			}
  			catch ( IllegalAccessException e ) {
  			  System.out.println("Unable to instantiate value help class");
  				throw new org.omg.CORBA.MARSHAL();
  			}
  			
  			//wirte fianlly
  			write_value(value, helper);
			}
			else {
			  write_value( value );
			}
    }

    public void write_value(com.cognition.util.j2me.J2MESerializable value, Class clz) {
      write_value( value );
    }

    public void write_value(com.cognition.util.j2me.J2MESerializable value, org.omg.CORBA.portable.BoxedValueHelper factory) {
      
      // Check if null value
  		if ( value == null ) {
  			write_long(0);
  			return;		
  		}
  		
			// Check if the value is already marshalled
			java.lang.Integer offset = ( java.lang.Integer ) valueList.get( value ); 
			if ( offset != null ) {
			  //already marshlled, then write offset
				write_long(0xffffffff);
				write_long( offset.intValue() );
				return;
			}
		  
			valueList.put( value, new Integer( nextByteToWrite ) );
			
		  int tag = 0;
			String uri = getURLCodeBase();
			
      if ( uri == null ) {
			  tag = 0x7fffff00 + VALUE_NO_CODEBASE + SINGLE_TYPE_INFORMATION + NO_CHUNK;
			}
		  else {
			  tag = 0x7fffff00 + VALUE_CODEBASE + SINGLE_TYPE_INFORMATION + NO_CHUNK;
			}
			
		  write_long(tag);
		  if  ( uri != null ) {
  			if ( urlIndex != -1 ) {
  				write_ulong( 0xffffffff );
  				write_ulong( urlIndex );
  			}
  			else {
  			  padding( 4 );
  				urlIndex = nextByteToWrite;
  				write_string( uri );
  			}
		  }
		  
		  // Put Repository Id
  		write_string( factory.get_id() );
  				
  		// Then put value
  		factory.write_value(this, value);
    }

    public void write_abstract_interface(java.lang.Object object) {
    	if (object instanceof org.omg.CORBA.Object) {
    	    write_boolean(true);
    	    write_Object((org.omg.CORBA.Object)object);
    	}
    	else {
    	    write_boolean(false);
    	    write_value((com.cognition.util.j2me.J2MESerializable)object);
    	}
    }

    public void flush() {
    }
 
    public byte[] getBuffer() {
        return buffer;
    }

    public int getLength() {
        return nextByteToWrite + 1;
    }

    private void padding(int alignSize) {
        int extraBytesUsed = nextByteToWrite % alignSize;
        
        if (extraBytesUsed != 0){
//        	makeRoom(alignSize);
            nextByteToWrite += alignSize - extraBytesUsed;
        }
    }

    public void makeRoom(int size){
        
    	if( nextByteToWrite + size > buffer.length - 1 ){
    	    
    	    int newLength = buffer.length * 3/2 + 1;
    	    
    	    for(;;) {
    	        if( nextByteToWrite + size > newLength ) {
    	            newLength = newLength * 3/2 + 1;
    	        }
    	        else {
    	            break;
    	        }
    	    }
    	    
    		byte[] newBuffer = new byte[newLength];
    		
    		System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
    		this.buffer = null;
    		this.buffer = newBuffer;
    	}
    	
    }
    
    private String getURLCodeBase() {
      return null;
    }
}
